package main

import (
	"bytes"
	"encoding/xml"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strconv"
)

// 业务处理命令，必须唯一
const command = "DL"

// 自定义错误
var (
	ErrInvalidPieceSequence          = errors.New("invalid piece sequence, it start from one")
	ErrVersionNotFound               = errors.New("target version not found in repository file: primary.xml")
	ErrFileTooLarge                  = errors.New("too large file, it must be less than 2M")
	ErrInvalidDLRequestMessageLength = errors.New("invalid \"DL\" request message length, it must be 23byte")
)

func init() {}

func main() {}

// BizRequest 业务请求信息
type BizRequest struct {
	Command         string // 长度2
	DeviceID        string // 长度12
	Version         string // 长度5
	PackageSequence int    // 从1开始，长度4
}

// BizResponse 业务响应信息
type BizResponse struct {
	PackageCount    int // 长度4
	PackageSequence int // 长度4
	DataPiece       []byte
}

type repository struct {
	XMLName     xml.Name `xml:"files"`
	Latest      string   `xml:"latest,attr"`
	Files       []file   `xml:"file"`
	Description string   `xml:",innerxml"`
}

type file struct {
	XMLName  xml.Name `xml:"file"`
	Version  string   `xml:"version"`
	Filename string   `xml:"filename"`
}

// ProcessorRegistry 业务处理程序注册器
func ProcessorRegistry(processers map[string]func([]byte, *log.Logger, ...interface{}) ([]byte, error)) {
	processers[command] = Process
}

// Process 业务处理程序执行过程
func Process(message []byte, logger *log.Logger, exts ...interface{}) ([]byte, error) {
	logger.Printf(command+" message is: %s\n", message)
	var filesDir string
	var pieceSize int
	for _, v := range exts {
		switch v.(type) {
		case string:
			filesDir = v.(string)
		case int:
			pieceSize = v.(int)
		default:
			// 什么都不做
		}
	}
	// 解析请求报文
	reqObj, err := Unpack(message, logger)
	if err != nil {
		return nil, err
	}

	// 验证分片序列号合规性
	if reqObj.PackageSequence <= 0 {
		return nil, ErrInvalidPieceSequence
	}

	// 载入资源库索引文件
	repoFile, err := ioutil.ReadFile(filesDir + "primary.xml")
	if err != nil {
		return nil, err
	}
	files := new(repository)
	err = xml.Unmarshal(repoFile, &files)
	if err != nil {
		return nil, err
	}
	// 从索引文件中获取文件名
	var fileName string
	for _, v := range files.Files {
		if v.Version == reqObj.Version {
			fileName = v.Filename
			break
		}
	}
	// 判断是否在资源库文件中搜索到文件名
	if fileName == "" {
		return nil, ErrVersionNotFound
	}
	// 读取文件信息
	fileBytes, err := ioutil.ReadFile(filesDir + fileName)
	if err != nil {
		return nil, err
	}
	//// 过滤过大的资源文件，最小分片字节大小*最多分片数量，暨最大限制为2M
	if len(fileBytes) > 256*8192 {
		return nil, ErrFileTooLarge
	}
	fileStat, err := os.Stat(filesDir + fileName)
	if err != nil {
		return nil, err
	}
	//// 计算分片数量，以及最后一段数据的长度
	fileSize := int(fileStat.Size())        // 文件实际字节数
	fullPieceCount := fileSize / pieceSize  // 能够填满一整块分片的分片数量
	leftBytesLength := fileSize % pieceSize // 剩余不能填满一整块分片的字节长度
	var totalPieceCount int                 // 总计可以输出的分片数量
	if leftBytesLength > 0 {                // 如果剩余字节长度大于0，则总计数量加1
		totalPieceCount = fullPieceCount + 1
	}
	logger.Printf("\"file size\": %d, \"full piece count\": %d, \"left byte length\": %d, \"total piece count\": %d, \"get piece sequence\": %d", fileSize, fullPieceCount, leftBytesLength, totalPieceCount, reqObj.PackageSequence)

	// 组装请求分片数据
	var rData []byte
	if reqObj.PackageSequence < totalPieceCount { // 没有到最后一段数据
		rData = fileBytes[(reqObj.PackageSequence-1)*pieceSize : reqObj.PackageSequence*pieceSize]
	} else if reqObj.PackageSequence == totalPieceCount { // 处理最后一段数据
		rData = fileBytes[(reqObj.PackageSequence-1)*pieceSize : fileSize]
	}
	logger.Printf("\"piece length\": %d", len(rData))
	respObj := Pack(&BizResponse{
		PackageCount:    int(totalPieceCount),
		PackageSequence: reqObj.PackageSequence,
		DataPiece:       rData,
	}, logger)

	return respObj, nil
}

// Pack 业务数据打包
func Pack(data *BizResponse, logger *log.Logger) []byte {
	resp := make([]byte, 8)

	copy(resp[:4], []byte(fmt.Sprintf("%04d", data.PackageCount)))
	copy(resp[4:8], []byte(fmt.Sprintf("%04d", data.PackageSequence)))

	buffer := new(bytes.Buffer)
	buffer.Write(resp)
	buffer.Write(data.DataPiece)

	return buffer.Bytes()
}

// Unpack 业务数据拆包
func Unpack(messageBytes []byte, logger *log.Logger) (*BizRequest, error) {
	req := new(BizRequest)
	// 判断报文长度是否符合要求
	if len(messageBytes) != 23 {
		return nil, ErrInvalidDLRequestMessageLength
	}
	// 拆分报文
	req.Command = string(messageBytes[:2])
	req.DeviceID = string(messageBytes[2:14])
	req.Version = string(messageBytes[14:19])
	seqNum, err := strconv.Atoi(string(messageBytes[19:23]))
	if err != nil {
		return nil, err
	}
	req.PackageSequence = seqNum
	return req, nil
}
